/********************************************************************* * * Copyright (C) 2002 Andrew Khan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ***************************************************************************/ package jxl.biff; import java.text.DateFormat; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.NumberFormat; import java.text.SimpleDateFormat; import jxl.common.Assert; import jxl.common.Logger; import jxl.WorkbookSettings; import jxl.format.Alignment; import jxl.format.Border; import jxl.format.BorderLineStyle; import jxl.format.CellFormat; import jxl.format.Colour; import jxl.format.Font; import jxl.format.Format; import jxl.format.Orientation; import jxl.format.Pattern; import jxl.format.VerticalAlignment; import jxl.read.biff.Record; /** * Holds an extended formatting record */ public class XFRecord extends WritableRecordData implements CellFormat { /** * The logger */ private static Logger logger = Logger.getLogger(XFRecord.class); /** * The index to the format record */ public int formatIndex; /** * The index of the parent format */ private int parentFormat; /** * The format type */ private XFType xfFormatType; /** * Indicates whether this is a date formatting record */ private boolean date; /** * Indicates whether this is a number formatting record */ private boolean number; /** * The date format for this record. Deduced when the record is * read in from a spreadsheet */ private DateFormat dateFormat; /** * The number format for this record. Deduced when the record is read in * from a spreadsheet */ private NumberFormat numberFormat; /** * The used attribute. Needs to be preserved in order to get accurate * rationalization */ private byte usedAttributes; /** * The index to the font record used by this XF record */ private int fontIndex; /** * Flag to indicate whether this XF record represents a locked cell */ private boolean locked; /** * Flag to indicate whether this XF record is hidden */ private boolean hidden; /** * The alignment for this cell (left, right, centre) */ private Alignment align; /** * The vertical alignment for the cell (top, bottom, centre) */ private VerticalAlignment valign; /** * The orientation of the cell */ private Orientation orientation; /** * Flag to indicates whether the data (normally text) in the cell will be * wrapped around to fit in the cell width */ private boolean wrap; /** * Indentation of the cell text */ private int indentation; /** * Flag to indicate that this format is shrink to fit */ private boolean shrinkToFit; /** * The border indicator for the left of this cell */ private BorderLineStyle leftBorder; /** * The border indicator for the right of the cell */ private BorderLineStyle rightBorder; /** * The border indicator for the top of the cell */ private BorderLineStyle topBorder; /** * The border indicator for the bottom of the cell */ private BorderLineStyle bottomBorder; /** * The border colour for the left of the cell */ private Colour leftBorderColour; /** * The border colour for the right of the cell */ private Colour rightBorderColour; /** * The border colour for the top of the cell */ private Colour topBorderColour; /** * The border colour for the bottom of the cell */ private Colour bottomBorderColour; /** * The background colour */ private Colour backgroundColour; /** * The background pattern */ private Pattern pattern; /** * The options mask which is used to store the processed cell options (such * as alignment, borders etc) */ private int options; /** * The index of this XF record within the workbook */ private int xfIndex; /** * The font object for this XF record */ private FontRecord font; /** * The format object for this XF record. This is used when creating * a writable record */ private DisplayFormat format; /** * Flag to indicate whether this XF record has been initialized */ private boolean initialized; /** * Indicates whether this cell was constructed by an API or read * from an existing Excel file */ private boolean read; /** * The excel format for this record. This is used to display the actual * excel format string back to the user (eg. when generating certain * types of XML document) as opposed to the java equivalent */ private Format excelFormat; /** * Flag to indicate whether the format information has been initialized. * This is false if the xf record has been read in, but true if it * has been written */ private boolean formatInfoInitialized; /** * Flag to indicate whether this cell was copied. If it was copied, then * it can be set to uninitialized, allowing us to change certain format * information */ private boolean copied; /** * A handle to the formatting records. The purpose of this is * to read the formatting information back, for the purposes of * output eg. to some form of XML */ private FormattingRecords formattingRecords; /** * Constants for the used attributes */ private static final int USE_FONT = 0x4; private static final int USE_FORMAT = 0x8; private static final int USE_ALIGNMENT = 0x10; private static final int USE_BORDER = 0x20; private static final int USE_BACKGROUND = 0x40; private static final int USE_PROTECTION = 0x80; private static final int USE_DEFAULT_VALUE=0xf8; /** * The list of built in date format values */ private static final int[] dateFormats = new int[] {0xe, 0xf, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x2d, 0x2e, 0x2f}; /** * The list of java date format equivalents */ private static final DateFormat[] javaDateFormats = new DateFormat[] {SimpleDateFormat.getDateInstance(DateFormat.SHORT), SimpleDateFormat.getDateInstance(DateFormat.MEDIUM), new SimpleDateFormat("d-MMM"), new SimpleDateFormat("MMM-yy"), new SimpleDateFormat("h:mm a"), new SimpleDateFormat("h:mm:ss a"), new SimpleDateFormat("H:mm"), new SimpleDateFormat("H:mm:ss"), new SimpleDateFormat("M/d/yy H:mm"), new SimpleDateFormat("mm:ss"), new SimpleDateFormat("H:mm:ss"), new SimpleDateFormat("mm:ss.S")}; /** * The list of built in number format values */ private static int[] numberFormats = new int[] {0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x30}; /** * The list of java number format equivalents */ private static NumberFormat[] javaNumberFormats = new NumberFormat[] {new DecimalFormat("0"), new DecimalFormat("0.00"), new DecimalFormat("#,##0"), new DecimalFormat("#,##0.00"), new DecimalFormat("$#,##0;($#,##0)"), new DecimalFormat("$#,##0;($#,##0)"), new DecimalFormat("$#,##0.00;($#,##0.00)"), new DecimalFormat("$#,##0.00;($#,##0.00)"), new DecimalFormat("0%"), new DecimalFormat("0.00%"), new DecimalFormat("0.00E00"), new DecimalFormat("#,##0;(#,##0)"), new DecimalFormat("#,##0;(#,##0)"), new DecimalFormat("#,##0.00;(#,##0.00)"), new DecimalFormat("#,##0.00;(#,##0.00)"), new DecimalFormat("#,##0;(#,##0)"), new DecimalFormat("$#,##0;($#,##0)"), new DecimalFormat("#,##0.00;(#,##0.00)"), new DecimalFormat("$#,##0.00;($#,##0.00)"), new DecimalFormat("##0.0E0")}; // Type to distinguish between biff7 and biff8 private static class BiffType {}; public static final BiffType biff8 = new BiffType(); public static final BiffType biff7 = new BiffType(); /** * The biff type */ private BiffType biffType; // Type to distinguish between cell and style records private static class XFType { } protected static final XFType cell = new XFType(); protected static final XFType style = new XFType(); /** * Constructs this object from the raw data * * @param t the raw data * @param bt the biff type */ public XFRecord(Record t, WorkbookSettings ws, BiffType bt) { super(t); biffType = bt; byte[] data = getRecord().getData(); fontIndex = IntegerHelper.getInt(data[0], data[1]); formatIndex = IntegerHelper.getInt(data[2], data[3]); date = false; number = false; // Compare against the date formats for (int i = 0; i < dateFormats.length && date == false; i++) { if (formatIndex == dateFormats[i]) { date = true; dateFormat = javaDateFormats[i]; } } // Compare against the number formats for (int i = 0; i < numberFormats.length && number == false; i++) { if (formatIndex == numberFormats[i]) { number = true; DecimalFormat df = (DecimalFormat) javaNumberFormats[i].clone(); DecimalFormatSymbols symbols = new DecimalFormatSymbols(ws.getLocale()); df.setDecimalFormatSymbols(symbols); numberFormat = df; //numberFormat = javaNumberFormats[i]; } } // Initialize the parent format and the type int cellAttributes = IntegerHelper.getInt(data[4], data[5]); parentFormat = (cellAttributes & 0xfff0) >> 4; int formatType = cellAttributes & 0x4; xfFormatType = formatType == 0 ? cell : style; locked = ((cellAttributes & 0x1) != 0); hidden = ((cellAttributes & 0x2) != 0); if (xfFormatType == cell && (parentFormat & 0xfff) == 0xfff) { // Something is screwy with the parent format - set to zero parentFormat = 0; logger.warn("Invalid parent format found - ignoring"); } initialized = false; read = true; formatInfoInitialized = false; copied = false; } /** * A constructor used when creating a writable record * * @param fnt the font * @param form the format */ public XFRecord(FontRecord fnt, DisplayFormat form) { super(Type.XF); initialized = false; locked = true; hidden = false; align = Alignment.GENERAL; valign = VerticalAlignment.BOTTOM; orientation = Orientation.HORIZONTAL; wrap = false; leftBorder = BorderLineStyle.NONE; rightBorder = BorderLineStyle.NONE; topBorder = BorderLineStyle.NONE; bottomBorder = BorderLineStyle.NONE; leftBorderColour = Colour.AUTOMATIC; rightBorderColour = Colour.AUTOMATIC; topBorderColour = Colour.AUTOMATIC; bottomBorderColour = Colour.AUTOMATIC; pattern = Pattern.NONE; backgroundColour = Colour.DEFAULT_BACKGROUND; indentation = 0; shrinkToFit = false; usedAttributes = (byte) (USE_FONT | USE_FORMAT | USE_BACKGROUND | USE_ALIGNMENT | USE_BORDER); // This will be set by the initialize method and the subclass respectively parentFormat = 0; xfFormatType = null; font = fnt; format = form; biffType = biff8; read = false; copied = false; formatInfoInitialized = true; Assert.verify(font != null); Assert.verify(format != null); } /** * Copy constructor. Used for copying writable formats, typically * when duplicating formats to handle merged cells * * @param fmt XFRecord */ protected XFRecord(XFRecord fmt) { super(Type.XF); initialized = false; locked = fmt.locked; hidden = fmt.hidden; align = fmt.align; valign = fmt.valign; orientation = fmt.orientation; wrap = fmt.wrap; leftBorder = fmt.leftBorder; rightBorder = fmt.rightBorder; topBorder = fmt.topBorder; bottomBorder = fmt.bottomBorder; leftBorderColour = fmt.leftBorderColour; rightBorderColour = fmt.rightBorderColour; topBorderColour = fmt.topBorderColour; bottomBorderColour = fmt.bottomBorderColour; pattern = fmt.pattern; xfFormatType = fmt.xfFormatType; indentation = fmt.indentation; shrinkToFit = fmt.shrinkToFit; parentFormat = fmt.parentFormat; backgroundColour = fmt.backgroundColour; // Shallow copy is sufficient for these purposes font = fmt.font; format = fmt.format; fontIndex = fmt.fontIndex; formatIndex = fmt.formatIndex; formatInfoInitialized = fmt.formatInfoInitialized; biffType = biff8; read = false; copied = true; } /** * A public copy constructor which can be used for copy formats between * different sheets. Unlike the the other copy constructor, this * version does a deep copy * * @param cellFormat the format to copy */ protected XFRecord(CellFormat cellFormat) { super(Type.XF); Assert.verify(cellFormat != null); Assert.verify(cellFormat instanceof XFRecord); XFRecord fmt = (XFRecord) cellFormat; if (!fmt.formatInfoInitialized) { fmt.initializeFormatInformation(); } locked = fmt.locked; hidden = fmt.hidden; align = fmt.align; valign = fmt.valign; orientation = fmt.orientation; wrap = fmt.wrap; leftBorder = fmt.leftBorder; rightBorder = fmt.rightBorder; topBorder = fmt.topBorder; bottomBorder = fmt.bottomBorder; leftBorderColour = fmt.leftBorderColour; rightBorderColour = fmt.rightBorderColour; topBorderColour = fmt.topBorderColour; bottomBorderColour = fmt.bottomBorderColour; pattern = fmt.pattern; xfFormatType = fmt.xfFormatType; parentFormat = fmt.parentFormat; indentation = fmt.indentation; shrinkToFit = fmt.shrinkToFit; backgroundColour = fmt.backgroundColour; // Deep copy of the font font = new FontRecord(fmt.getFont()); // Copy the format if (fmt.getFormat() == null) { // format is writable if (fmt.format.isBuiltIn()) { format = fmt.format; } else { // Format is not built in, so do a deep copy format = new FormatRecord((FormatRecord) fmt.format); } } else if (fmt.getFormat() instanceof BuiltInFormat) { // read excel format is built in excelFormat = (BuiltInFormat) fmt.excelFormat; format = (BuiltInFormat) fmt.excelFormat; } else { // read excel format is user defined Assert.verify(fmt.formatInfoInitialized); // in this case FormattingRecords should initialize the excelFormat // field with an instance of FormatRecord Assert.verify(fmt.excelFormat instanceof FormatRecord); // Format is not built in, so do a deep copy FormatRecord fr = new FormatRecord((FormatRecord) fmt.excelFormat); // Set both format fields to be the same object, since // FormatRecord implements all the necessary interfaces excelFormat = fr; format = fr; } biffType = biff8; // The format info should be all OK by virtue of the deep copy formatInfoInitialized = true; // This format was not read in read = false; // Treat this as a new cell record, so set the copied flag to false copied = false; // The font or format indexes need to be set, so set initialized to false initialized = false; } /** * Gets the java date format for this format record * * @return returns the date format */ public DateFormat getDateFormat() { return dateFormat; } /** * Gets the java number format for this format record * * @return returns the number format */ public NumberFormat getNumberFormat() { return numberFormat; } /** * Gets the lookup number of the format record * * @return returns the lookup number of the format record */ public int getFormatRecord() { return formatIndex; } /** * Sees if this format is a date format * * @return TRUE if this refers to a built in date format */ public boolean isDate() { return date; } /** * Sees if this format is a number format * * @return TRUE if this refers to a built in date format */ public boolean isNumber() { return number; } /** * Converts the various fields into binary data. If this object has * been read from an Excel file rather than being requested by a user (ie. * if the read flag is TRUE) then * no processing takes place and the raw data is simply returned. * * @return the raw data for writing */ public byte[] getData() { // Format rationalization process means that we always want to // regenerate the format info - even if the spreadsheet was // read in if (!formatInfoInitialized) { initializeFormatInformation(); } byte[] data = new byte[20]; IntegerHelper.getTwoBytes(fontIndex, data, 0); IntegerHelper.getTwoBytes(formatIndex, data, 2); // Do the cell attributes int cellAttributes = 0; if (getLocked()) { cellAttributes |= 0x01; } if (getHidden()) { cellAttributes |= 0x02; } if (xfFormatType == style) { cellAttributes |= 0x04; parentFormat = 0xffff; } cellAttributes |= (parentFormat << 4); IntegerHelper.getTwoBytes(cellAttributes, data, 4); int alignMask = align.getValue(); if (wrap) { alignMask |= 0x08; } alignMask |= (valign.getValue() << 4); alignMask |= (orientation.getValue() << 8); IntegerHelper.getTwoBytes(alignMask, data, 6); data[9] = (byte) 0x10; // Set the borders int borderMask = leftBorder.getValue(); borderMask |= (rightBorder.getValue() << 4); borderMask |= (topBorder.getValue() << 8); borderMask |= (bottomBorder.getValue() << 12); IntegerHelper.getTwoBytes(borderMask, data, 10); // Set the border palette information if border mask is non zero // Hard code the colours to be black if (borderMask != 0) { byte lc = (byte)leftBorderColour.getValue(); byte rc = (byte)rightBorderColour.getValue(); byte tc = (byte)topBorderColour.getValue(); byte bc = (byte)bottomBorderColour.getValue(); int sideColourMask = (lc & 0x7f) | ((rc & 0x7f) << 7); int topColourMask = (tc & 0x7f) | ((bc & 0x7f) << 7); IntegerHelper.getTwoBytes(sideColourMask, data, 12); IntegerHelper.getTwoBytes(topColourMask, data, 14); } // Set the background pattern int patternVal = pattern.getValue() << 10; IntegerHelper.getTwoBytes(patternVal, data, 16); // Set the colour palette int colourPaletteMask = backgroundColour.getValue(); colourPaletteMask |= (0x40 << 7); IntegerHelper.getTwoBytes(colourPaletteMask, data, 18); // Set the cell options options |= indentation & 0x0f; if (shrinkToFit) { options |= 0x10; } else { options &= 0xef; } data[8] = (byte) options; if (biffType == biff8) { data[9] = (byte) usedAttributes; } return data; } /** * Accessor for the locked flag * * @return TRUE if this XF record locks cells, FALSE otherwise */ protected final boolean getLocked() { return locked; } /** * Accessor for the hidden flag * * @return TRUE if this XF record hides the cell, FALSE otherwise */ protected final boolean getHidden() { return hidden; } /** * Sets whether or not this XF record locks the cell * * @param l the locked flag */ protected final void setXFLocked(boolean l) { locked = l; usedAttributes |= USE_PROTECTION; } /** * Sets the cell options * * @param opt the cell options */ protected final void setXFCellOptions(int opt) { options |= opt; } /** * Sets the horizontal alignment for the data in this cell. * This method should only be called from its writable subclass * CellXFRecord * * @param a the alignment */ protected void setXFAlignment(Alignment a) { Assert.verify(!initialized); align = a; usedAttributes |= USE_ALIGNMENT; } /** * Sets the indentation * * @param i the indentation */ protected void setXFIndentation(int i) { Assert.verify(!initialized); indentation = i; usedAttributes |= USE_ALIGNMENT; } /** * Sets the shrink to fit flag * * @param s the shrink to fit flag */ protected void setXFShrinkToFit(boolean s) { Assert.verify(!initialized); shrinkToFit = s; usedAttributes |= USE_ALIGNMENT; } /** * Gets the horizontal cell alignment * * @return the alignment */ public Alignment getAlignment() { if (!formatInfoInitialized) { initializeFormatInformation(); } return align; } /** * Gets the indentation * * @return the indentation */ public int getIndentation() { if (!formatInfoInitialized) { initializeFormatInformation(); } return indentation; } /** * Gets the shrink to fit flag * * @return TRUE if this format is shrink to fit, FALSE otherise */ public boolean isShrinkToFit() { if (!formatInfoInitialized) { initializeFormatInformation(); } return shrinkToFit; } /** * Accessor for whether a particular cell is locked * * @return TRUE if this cell is locked, FALSE otherwise */ public boolean isLocked() { if (!formatInfoInitialized) { initializeFormatInformation(); } return locked; } /** * Gets the vertical cell alignment * * @return the alignment */ public VerticalAlignment getVerticalAlignment() { if (!formatInfoInitialized) { initializeFormatInformation(); } return valign; } /** * Gets the orientation * * @return the orientation */ public Orientation getOrientation() { if (!formatInfoInitialized) { initializeFormatInformation(); } return orientation; } /** * Sets the horizontal alignment for the data in this cell. * This method should only be called from its writable subclass * CellXFRecord * * @param c the background colour * @param p the background pattern */ protected void setXFBackground(Colour c, Pattern p) { Assert.verify(!initialized); backgroundColour = c; pattern = p; usedAttributes |= USE_BACKGROUND; } /** * Gets the background colour used by this cell * * @return the foreground colour */ public Colour getBackgroundColour() { if (!formatInfoInitialized) { initializeFormatInformation(); } return backgroundColour; } /** * Gets the pattern used by this cell format * * @return the background pattern */ public Pattern getPattern() { if (!formatInfoInitialized) { initializeFormatInformation(); } return pattern; } /** * Sets the vertical alignment for the data in this cell * This method should only be called from its writable subclass * CellXFRecord * * @param va the vertical alignment */ protected void setXFVerticalAlignment(VerticalAlignment va) { Assert.verify(!initialized); valign = va; usedAttributes |= USE_ALIGNMENT; } /** * Sets the vertical alignment for the data in this cell * This method should only be called from its writable subclass * CellXFRecord * * @param o the orientation */ protected void setXFOrientation(Orientation o) { Assert.verify(!initialized); orientation = o; usedAttributes |= USE_ALIGNMENT; } /** * Sets whether the data in this cell is wrapped * This method should only be called from its writable subclass * CellXFRecord * * @param w the wrap flag */ protected void setXFWrap(boolean w) { Assert.verify(!initialized); wrap = w; usedAttributes |= USE_ALIGNMENT; } /** * Gets whether or not the contents of this cell are wrapped * * @return TRUE if this cell's contents are wrapped, FALSE otherwise */ public boolean getWrap() { if (!formatInfoInitialized) { initializeFormatInformation(); } return wrap; } /** * Sets the border for this cell * This method should only be called from its writable subclass * CellXFRecord * * @param b the border * @param ls the border line style */ protected void setXFBorder(Border b, BorderLineStyle ls, Colour c) { Assert.verify(!initialized); if (c == Colour.BLACK || c == Colour.UNKNOWN) { c = Colour.PALETTE_BLACK; } if (b == Border.LEFT) { leftBorder = ls; leftBorderColour = c; } else if (b == Border.RIGHT) { rightBorder = ls; rightBorderColour = c; } else if (b == Border.TOP) { topBorder = ls; topBorderColour = c; } else if (b == Border.BOTTOM) { bottomBorder = ls; bottomBorderColour = c; } usedAttributes |= USE_BORDER; return; } /** * Gets the line style for the given cell border * If a border type of ALL or NONE is specified, then a line style of * NONE is returned * * @param border the cell border we are interested in * @return the line style of the specified border */ public BorderLineStyle getBorder(Border border) { return getBorderLine(border); } /** * Gets the line style for the given cell border * If a border type of ALL or NONE is specified, then a line style of * NONE is returned * * @param border the cell border we are interested in * @return the line style of the specified border */ public BorderLineStyle getBorderLine(Border border) { // Don't bother with the short cut records if (border == Border.NONE || border == Border.ALL) { return BorderLineStyle.NONE; } if (!formatInfoInitialized) { initializeFormatInformation(); } if (border == Border.LEFT) { return leftBorder; } else if (border == Border.RIGHT) { return rightBorder; } else if (border == Border.TOP) { return topBorder; } else if (border == Border.BOTTOM) { return bottomBorder; } return BorderLineStyle.NONE; } /** * Gets the line style for the given cell border * If a border type of ALL or NONE is specified, then a line style of * NONE is returned * * @param border the cell border we are interested in * @return the line style of the specified border */ public Colour getBorderColour(Border border) { // Don't bother with the short cut records if (border == Border.NONE || border == Border.ALL) { return Colour.PALETTE_BLACK; } if (!formatInfoInitialized) { initializeFormatInformation(); } if (border == Border.LEFT) { return leftBorderColour; } else if (border == Border.RIGHT) { return rightBorderColour; } else if (border == Border.TOP) { return topBorderColour; } else if (border == Border.BOTTOM) { return bottomBorderColour; } return Colour.BLACK; } /** * Determines if this cell format has any borders at all. Used to * set the new borders when merging a group of cells * * @return TRUE if this cell has any borders, FALSE otherwise */ public final boolean hasBorders() { if (!formatInfoInitialized) { initializeFormatInformation(); } if (leftBorder == BorderLineStyle.NONE && rightBorder == BorderLineStyle.NONE && topBorder == BorderLineStyle.NONE && bottomBorder == BorderLineStyle.NONE) { return false; } return true; } /** * If this cell has not been read in from an existing Excel sheet, * then initializes this record with the XF index passed in. Calls * initialized on the font and format record * * @param pos the xf index to initialize this record with * @param fr the containing formatting records * @param fonts the container for the fonts * @exception NumFormatRecordsException */ public final void initialize(int pos, FormattingRecords fr, Fonts fonts) throws NumFormatRecordsException { xfIndex = pos; formattingRecords = fr; // If this file has been read in or copied, // the font and format indexes will // already be initialized, so just set the initialized flag and // return if (read || copied) { initialized = true; return; } if (!font.isInitialized()) { fonts.addFont(font); } if (!format.isInitialized()) { fr.addFormat(format); } fontIndex = font.getFontIndex(); formatIndex = format.getFormatIndex(); initialized = true; } /** * Resets the initialize flag. This is called by the constructor of * WritableWorkbookImpl to reset the statically declared fonts */ public final void uninitialize() { // As the default formats are cloned internally, the initialized // flag should never be anything other than false if (initialized == true) { logger.warn("A default format has been initialized"); } initialized = false; } /** * Sets the XF index. Called when rationalizing the XF records * immediately prior to writing * * @param xfi the new xf index */ final void setXFIndex(int xfi) { xfIndex = xfi; } /** * Accessor for the XF index * * @return the XF index for this cell */ public final int getXFIndex() { return xfIndex; } /** * Accessor to see if this format is initialized * * @return TRUE if this format is initialized, FALSE otherwise */ public final boolean isInitialized() { return initialized; } /** * Accessor to see if this format was read in. Used when checking merged * cells * * @return TRUE if this XF record was read in, FALSE if it was generated by * the user API */ public final boolean isRead() { return read; } /** * Gets the format used by this format * * @return the format */ public Format getFormat() { if (!formatInfoInitialized) { initializeFormatInformation(); } return excelFormat; } /** * Gets the font used by this format * * @return the font */ public Font getFont() { if (!formatInfoInitialized) { initializeFormatInformation(); } return font; } /** * Initializes the internal format information from the data read in */ private void initializeFormatInformation() { // Initialize the cell format string if (formatIndex < BuiltInFormat.builtIns.length && BuiltInFormat.builtIns[formatIndex] != null) { excelFormat = BuiltInFormat.builtIns[formatIndex]; } else { excelFormat = formattingRecords.getFormatRecord(formatIndex); } // Initialize the font font = formattingRecords.getFonts().getFont(fontIndex); // Initialize the cell format data from the binary record byte[] data = getRecord().getData(); // Get the parent record int cellAttributes = IntegerHelper.getInt(data[4], data[5]); parentFormat = (cellAttributes & 0xfff0) >> 4; int formatType = cellAttributes & 0x4; xfFormatType = formatType == 0 ? cell : style; locked = ((cellAttributes & 0x1) != 0); hidden = ((cellAttributes & 0x2) != 0); if (xfFormatType == cell && (parentFormat & 0xfff) == 0xfff) { // Something is screwy with the parent format - set to zero parentFormat = 0; logger.warn("Invalid parent format found - ignoring"); } int alignMask = IntegerHelper.getInt(data[6], data[7]); // Get the wrap if ((alignMask & 0x08) != 0) { wrap = true; } // Get the horizontal alignment align = Alignment.getAlignment(alignMask & 0x7); // Get the vertical alignment valign = VerticalAlignment.getAlignment((alignMask >> 4) & 0x7); // Get the orientation orientation = Orientation.getOrientation((alignMask >> 8) & 0xff); int attr = IntegerHelper.getInt(data[8], data[9]); // Get the indentation indentation = attr & 0x0F; // Get the shrink to fit flag shrinkToFit = (attr & 0x10) != 0; // Get the used attribute if (biffType == biff8) { usedAttributes = data[9]; } // Get the borders int borderMask = IntegerHelper.getInt(data[10], data[11]); leftBorder = BorderLineStyle.getStyle(borderMask & 0x7); rightBorder = BorderLineStyle.getStyle((borderMask >> 4) & 0x7); topBorder = BorderLineStyle.getStyle((borderMask >> 8) & 0x7); bottomBorder = BorderLineStyle.getStyle((borderMask >> 12) & 0x7); int borderColourMask = IntegerHelper.getInt(data[12], data[13]); leftBorderColour = Colour.getInternalColour(borderColourMask & 0x7f); rightBorderColour = Colour.getInternalColour ((borderColourMask & 0x3f80) >> 7); borderColourMask = IntegerHelper.getInt(data[14], data[15]); topBorderColour = Colour.getInternalColour(borderColourMask & 0x7f); bottomBorderColour = Colour.getInternalColour ((borderColourMask & 0x3f80) >> 7); if (biffType == biff8) { // Get the background pattern. This is the six most significant bits int patternVal = IntegerHelper.getInt(data[16], data[17]); patternVal = patternVal & 0xfc00; patternVal = patternVal >> 10; pattern = Pattern.getPattern(patternVal); // Get the background colour int colourPaletteMask = IntegerHelper.getInt(data[18], data[19]); backgroundColour = Colour.getInternalColour(colourPaletteMask & 0x3f); if (backgroundColour == Colour.UNKNOWN || backgroundColour == Colour.DEFAULT_BACKGROUND1) { backgroundColour = Colour.DEFAULT_BACKGROUND; } } else { pattern = Pattern.NONE; backgroundColour = Colour.DEFAULT_BACKGROUND; } // Set the lazy initialization flag formatInfoInitialized = true; } /** * Standard hash code implementation * @return the hash code */ public int hashCode() { // Must have its formats info initialized in order to compute the hash code if (!formatInfoInitialized) { initializeFormatInformation(); } int hashValue = 17; int oddPrimeNumber = 37; // The boolean fields hashValue = oddPrimeNumber*hashValue + (hidden ? 1:0); hashValue = oddPrimeNumber*hashValue + (locked ? 1:0); hashValue = oddPrimeNumber*hashValue + (wrap ? 1:0); hashValue = oddPrimeNumber*hashValue + (shrinkToFit ? 1:0); // The enumerations if (xfFormatType == cell) { hashValue = oddPrimeNumber*hashValue + 1; } else if (xfFormatType == style) { hashValue = oddPrimeNumber*hashValue + 2; } hashValue = oddPrimeNumber*hashValue + (align.getValue() + 1); hashValue = oddPrimeNumber*hashValue + (valign.getValue() + 1); hashValue = oddPrimeNumber*hashValue + (orientation.getValue()); hashValue ^= leftBorder.getDescription().hashCode(); hashValue ^= rightBorder.getDescription().hashCode(); hashValue ^= topBorder.getDescription().hashCode(); hashValue ^= bottomBorder.getDescription().hashCode(); hashValue = oddPrimeNumber*hashValue + (leftBorderColour.getValue()); hashValue = oddPrimeNumber*hashValue + (rightBorderColour.getValue()); hashValue = oddPrimeNumber*hashValue + (topBorderColour.getValue()); hashValue = oddPrimeNumber*hashValue + (bottomBorderColour.getValue()); hashValue = oddPrimeNumber*hashValue + (backgroundColour.getValue()); hashValue = oddPrimeNumber*hashValue + (pattern.getValue() + 1); // The integer fields hashValue = oddPrimeNumber*hashValue + usedAttributes; hashValue = oddPrimeNumber*hashValue + parentFormat; hashValue = oddPrimeNumber*hashValue + fontIndex; hashValue = oddPrimeNumber*hashValue + formatIndex; hashValue = oddPrimeNumber*hashValue + indentation; return hashValue; } /** * Equals method. This is called when comparing writable formats * in order to prevent duplicate formats being added to the workbook * * @param o object to compare * @return TRUE if the objects are equal, FALSE otherwise */ public boolean equals(Object o) { if (o == this) { return true; } if (!(o instanceof XFRecord)) { return false; } XFRecord xfr = (XFRecord) o; // Both records must be writable and have their format info initialized if (!formatInfoInitialized) { initializeFormatInformation(); } if (!xfr.formatInfoInitialized) { xfr.initializeFormatInformation(); } if (xfFormatType != xfr.xfFormatType || parentFormat != xfr.parentFormat || locked != xfr.locked || hidden != xfr.hidden || usedAttributes != xfr.usedAttributes) { return false; } if (align != xfr.align || valign != xfr.valign || orientation != xfr.orientation || wrap != xfr.wrap || shrinkToFit != xfr.shrinkToFit || indentation != xfr.indentation) { return false; } if (leftBorder != xfr.leftBorder || rightBorder != xfr.rightBorder || topBorder != xfr.topBorder || bottomBorder != xfr.bottomBorder) { return false; } if (leftBorderColour != xfr.leftBorderColour || rightBorderColour != xfr.rightBorderColour || topBorderColour != xfr.topBorderColour || bottomBorderColour != xfr.bottomBorderColour) { return false; } if (backgroundColour != xfr.backgroundColour || pattern != xfr.pattern) { return false; } if (initialized && xfr.initialized) { // Both formats are initialized, so it is sufficient to just do // shallow equals on font, format objects, // since we are testing for the presence of clones anwyay // Use indices rather than objects because of the rationalization // process (which does not set the object on an XFRecord) if (fontIndex != xfr.fontIndex || formatIndex != xfr.formatIndex) { return false; } } else { // Perform a deep compare of fonts and formats if (!font.equals(xfr.font) || !format.equals(xfr.format)) { return false; } } return true; } /** * Sets the format index. This is called during the rationalization process * when some of the duplicate number formats have been removed * @param newindex the new format index */ void setFormatIndex(int newindex) { formatIndex = newindex; } /** * Accessor for the font index. Called by the FormattingRecords objects * during the rationalization process * @return the font index */ public int getFontIndex() { return fontIndex; } /** * Sets the font index. This is called during the rationalization process * when some of the duplicate fonts have been removed * @param newindex the new index */ void setFontIndex(int newindex) { fontIndex = newindex; } /** * Sets the format type and parent format from the writable subclass * @param t the xf type * @param pf the parent format */ protected void setXFDetails(XFType t, int pf) { xfFormatType = t; parentFormat = pf; } /** * Changes the appropriate indexes during the rationalization process * @param xfMapping the xf index re-mappings */ void rationalize(IndexMapping xfMapping) { xfIndex = xfMapping.getNewIndex(xfIndex); if (xfFormatType == cell) { parentFormat = xfMapping.getNewIndex(parentFormat); } } /** * Sets the font object with a workbook specific clone. Called from * the CellValue object when the font has been identified as a statically * shared font * Also called to superimpose a HyperlinkFont on an existing label cell */ public void setFont(FontRecord f) { // This style cannot be initialized, otherwise it would mean it would // have been initialized with shared font // However, sometimes (when setting a row or column format) an initialized // XFRecord may have its font overridden by the column/row font = f; } }